﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Core;
using Core.Configuration;
using Core.Infrastructure;
using FluentValidation;
using FluentValidation.AspNetCore;
using Infrastructure.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Serialization;

namespace AKit.Framework.Infrastructure.Exceptions;

/// <summary>
/// Represents extensions of IServiceCollection
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Configure base application settings
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    /// <param name="builder">A builder for web applications and services</param>
    public static void ConfigureApplicationSettings(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        //create default file provider
        CommonHelper.DefaultFileProvider = new AKitFileProvider(builder.Environment);

        //register type finder
        var typeFinder = new WebAppTypeFinder();
        Singleton<ITypeFinder>.Instance = typeFinder;
        services.AddSingleton<ITypeFinder>(typeFinder);

        //bind general configuration
        services.BindApplicationSettings(builder);
    }

    /// <summary>
    /// Bind application settings
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    /// <param name="builder">A builder for web applications and services</param>
    public static void BindApplicationSettings(this IServiceCollection services, WebApplicationBuilder builder)
    {
        var typeFinder = Singleton<ITypeFinder>.Instance;

        //add configuration parameters
        var configurations = typeFinder
            .FindClassesOfType<IConfig>()
            .Select(configType => (IConfig)Activator.CreateInstance(configType))
            .ToList();

        foreach (var config in configurations)
            builder.Configuration.GetSection(config.Name).Bind(config, options => options.BindNonPublicProperties = true);

        var appSettings = Singleton<AppSettings>.Instance;

        if (appSettings == null)
        {
            appSettings = AppSettingsHelper.SaveAppSettings(configurations, CommonHelper.DefaultFileProvider, false);
            services.AddSingleton(appSettings);
        }
        else
        {
            var needToUpdate = configurations.Any(conf => !appSettings.Configuration.ContainsKey(conf.Name));
            AppSettingsHelper.SaveAppSettings(configurations, CommonHelper.DefaultFileProvider, needToUpdate);
        }
    }

    /// <summary>
    /// Add services to the application and configure service provider
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    /// <param name="builder">A builder for web applications and services</param>
    public static void ConfigureApplicationServices(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        //add accessor to HttpContext
        services.AddHttpContextAccessor();

        //initialize plugins
        var mvcCoreBuilder = services.AddMvcCore();
        mvcCoreBuilder.PartManager.InitializePlugins(services);

        //bind plugins configurations
        services.BindApplicationSettings(builder);

        //create engine and configure service provider
        var engine = EngineContext.Create();

        builder.Services.AddRateLimiter();

        engine.ConfigureServices(services, builder.Configuration);
    }

    /// <summary>
    /// Register HttpContextAccessor
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddHttpContextAccessor(this IServiceCollection services)
    {
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    }

    /// <summary>
    /// Adds services required for anti-forgery support
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddAntiForgery(this IServiceCollection services)
    {
    }

    /// <summary>
    /// Adds services required for application session state
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddHttpSession(this IServiceCollection services)
    {
    }

    /// <summary>
    /// Adds services required for themes support
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddThemes(this IServiceCollection services)
    {
    }

    /// <summary>
    /// Adds services required for distributed cache
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddDistributedCache(this IServiceCollection services)
    {
        var appSettings = Singleton<AppSettings>.Instance;
        var distributedCacheConfig = appSettings.Get<DistributedCacheConfig>();

        if (!distributedCacheConfig.Enabled)
            return;

        switch (distributedCacheConfig.DistributedCacheType)
        {
            case DistributedCacheType.Memory:
                services.AddDistributedMemoryCache();
                break;

            case DistributedCacheType.SqlServer:
                services.AddDistributedSqlServerCache(options =>
                {
                    options.ConnectionString = distributedCacheConfig.ConnectionString;
                    options.SchemaName = distributedCacheConfig.SchemaName;
                    options.TableName = distributedCacheConfig.TableName;
                });
                break;

            case DistributedCacheType.Redis:
                services.AddStackExchangeRedisCache(options =>
                {
                    options.Configuration = distributedCacheConfig.ConnectionString;
                    options.InstanceName = distributedCacheConfig.InstanceName ?? string.Empty;
                });
                break;

            case DistributedCacheType.RedisSynchronizedMemory:
                services.AddStackExchangeRedisCache(options =>
                {
                    options.Configuration = distributedCacheConfig.ConnectionString;
                    options.InstanceName = distributedCacheConfig.InstanceName ?? string.Empty;
                });
                break;
        }
    }

    /// <summary>
    /// Adds data protection services
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddAKitDataProtection(this IServiceCollection services)
    {
        //var dataProtectionKeysPath = CommonHelper.DefaultFileProvider.MapPath(AKitDataProtectionDefaults.DataProtectionKeysPath);
        //var dataProtectionKeysFolder = new System.IO.DirectoryInfo(dataProtectionKeysPath);

        ////configure the data protection system to persist keys to the specified directory
        //services.AddDataProtection().PersistKeysToFileSystem(dataProtectionKeysFolder);
    }

    /// <summary>
    /// Adds authentication service
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddAKitAuthentication(this IServiceCollection services)
    {
        ////set default authentication schemes
        //var authenticationBuilder = services.AddAuthentication(options =>
        //{
        //    options.DefaultChallengeScheme = AKitAuthenticationDefaults.AuthenticationScheme;
        //    options.DefaultScheme = AKitAuthenticationDefaults.AuthenticationScheme;
        //    options.DefaultSignInScheme = AKitAuthenticationDefaults.ExternalAuthenticationScheme;
        //});

        ////add main cookie authentication
        //authenticationBuilder.AddCookie(AKitAuthenticationDefaults.AuthenticationScheme, options =>
        //{
        //    options.Cookie.Name = $"{AKitCookieDefaults.Prefix}{AKitCookieDefaults.AuthenticationCookie}";
        //    options.Cookie.HttpOnly = true;
        //    options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
        //    options.LoginPath = AKitAuthenticationDefaults.LoginPath;
        //    options.AccessDeniedPath = AKitAuthenticationDefaults.AccessDeniedPath;
        //    options.ReturnUrlParameter = AKitAuthenticationDefaults.ReturnUrlParameter;
        //});

        ////add external authentication
        //authenticationBuilder.AddCookie(AKitAuthenticationDefaults.ExternalAuthenticationScheme, options =>
        //{
        //    options.Cookie.Name = $"{AKitCookieDefaults.Prefix}{AKitCookieDefaults.ExternalAuthenticationCookie}";
        //    options.Cookie.HttpOnly = true;
        //    options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
        //    options.LoginPath = AKitAuthenticationDefaults.LoginPath;
        //    options.AccessDeniedPath = AKitAuthenticationDefaults.AccessDeniedPath;
        //    options.ReturnUrlParameter = AKitAuthenticationDefaults.ReturnUrlParameter;
        //});

        ////register and configure external authentication plugins now
        //var typeFinder = Singleton<ITypeFinder>.Instance;
        //var externalAuthConfigurations = typeFinder.FindClassesOfType<IExternalAuthenticationRegistrar>();
        //var externalAuthInstances = externalAuthConfigurations
        //    .Select(x => (IExternalAuthenticationRegistrar)Activator.CreateInstance(x));

        //foreach (var instance in externalAuthInstances)
        //    instance.Configure(authenticationBuilder);
    }

    /// <summary>
    /// Add and configure MVC for the application
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    /// <returns>A builder for configuring MVC services</returns>
    public static IMvcBuilder AddAKitMvc(this IServiceCollection services)
    {
        //add basic MVC feature
        var mvcBuilder = services.AddControllersWithViews();

        //mvcBuilder.AddRazorRuntimeCompilation();

        var appSettings = Singleton<AppSettings>.Instance;
        if (appSettings.Get<CommonConfig>().UseSessionStateTempDataProvider)
        {
            //use session-based temp data provider
            mvcBuilder.AddSessionStateTempDataProvider();
        }
        else
        {
            //use cookie-based temp data provider
            mvcBuilder.AddCookieTempDataProvider(options =>
            {
                //options.Cookie.Name = $"{AKitCookieDefaults.Prefix}{AKitCookieDefaults.TempDataCookie}";
                //options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
            });
        }

        services.AddRazorPages();

        //MVC now serializes JSON with camel case names by default, use this code to avoid it
        mvcBuilder.AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());

        //set some options
        mvcBuilder.AddMvcOptions(options =>
        {
            //options.ModelBinderProviders.Insert(1, new AKitModelBinderProvider());
            ////add custom display metadata provider 
            //options.ModelMetadataDetailsProviders.Add(new AKitMetadataProvider());

            ////in .NET model binding for a non-nullable property may fail with an error message "The value '' is invalid"
            ////here we set the locale name as the message, we'll replace it with the actual one later when not-null validation failed
            //options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(_ => AKitValidationDefaults.NotNullValidationLocaleName);
        });

        //add fluent validation
        services.AddFluentValidationAutoValidation().AddFluentValidationClientsideAdapters();

        //register all available validators from AKit assemblies
        var assemblies = mvcBuilder.PartManager.ApplicationParts
            .OfType<AssemblyPart>()
            .Where(part => part.Name.StartsWith("AKit", StringComparison.InvariantCultureIgnoreCase))
            .Select(part => part.Assembly);
        services.AddValidatorsFromAssemblies(assemblies);

        //register controllers as services, it'll allow to override them
        mvcBuilder.AddControllersAsServices();

        return mvcBuilder;
    }

    /// <summary>
    /// Register custom RedirectResultExecutor
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddAKitRedirectResultExecutor(this IServiceCollection services)
    {
        ////we use custom redirect executor as a workaround to allow using non-ASCII characters in redirect URLs
        //services.AddScoped<IActionResultExecutor<RedirectResult>, AKitRedirectResultExecutor>();
        //services.AddScoped<IActionResultExecutor<LocalRedirectResult>, AKitLocalRedirectResultExecutor>();
    }

    /// <summary>
    /// Add and configure WebMarkupMin service
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddAKitWebMarkupMin(this IServiceCollection services)
    {
        ////check whether database is installed
        //if (!DataSettingsManager.IsDatabaseInstalled())
        //    return;

        //services
        //    .AddWebMarkupMin(options =>
        //    {
        //        options.AllowMinificationInDevelopmentEnvironment = true;
        //        options.AllowCompressionInDevelopmentEnvironment = true;
        //        options.DisableMinification = !EngineContext.Current.Resolve<CommonSettings>().EnableHtmlMinification;
        //        options.DisableCompression = true;
        //        options.DisablePoweredByHttpHeaders = true;
        //    })
        //    .AddHtmlMinification(options =>
        //    {
        //        options.MinificationSettings.AttributeQuotesRemovalMode = HtmlAttributeQuotesRemovalMode.KeepQuotes;

        //        options.CssMinifierFactory = new NUglifyCssMinifierFactory();
        //        options.JsMinifierFactory = new NUglifyJsMinifierFactory();
        //    })
        //    .AddXmlMinification(options =>
        //    {
        //        var settings = options.MinificationSettings;
        //        settings.RenderEmptyTagsWithSpace = true;
        //        settings.CollapseTagsWithoutContent = true;
        //    });
    }

    /// <summary>
    /// Adds WebOptimizer to the specified <see cref="IServiceCollection"/> and enables CSS and JavaScript minification.
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddAKitWebOptimizer(this IServiceCollection services)
    {
        //var appSettings = Singleton<AppSettings>.Instance;
        //var woConfig = appSettings.Get<WebOptimizerConfig>();

        //if (!woConfig.EnableCssBundling && !woConfig.EnableJavaScriptBundling)
        //{
        //    services.AddScoped<IAKitAssetHelper, AKitDefaultAssetHelper>();
        //    return;
        //}

        ////add minification & bundling
        //var cssSettings = new CssBundlingSettings
        //{
        //    FingerprintUrls = false,
        //    Minify = woConfig.EnableCssBundling
        //};

        //var codeSettings = new CodeBundlingSettings
        //{
        //    Minify = woConfig.EnableJavaScriptBundling,
        //    AdjustRelativePaths = false //disable this feature because it breaks function names that have "Url(" at the end
        //};

        //services.AddWebOptimizer(null, cssSettings, codeSettings);
        //services.AddScoped<IAKitAssetHelper, AKitAssetHelper>();
    }

    /// <summary>
    /// Add and configure default HTTP clients
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    public static void AddAKitHttpClients(this IServiceCollection services)
    {
        ////default client
        //services.AddHttpClient(AKitHttpDefaults.DefaultHttpClient).WithProxy();

        ////client to request current store
        //services.AddHttpClient<StoreHttpClient>();

        ////client to request nopCommerce official site
        //services.AddHttpClient<AKitHttpClient>().WithProxy();

        ////client to request reCAPTCHA service
        //services.AddHttpClient<CaptchaHttpClient>().WithProxy();

        ////client to request artificial intelligence service
        //services.AddHttpClient<ArtificialIntelligenceHttpClient>().WithProxy();
    }
}
